// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <cmath>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "coin/proto/coin_query.pb.h"

namespace {

inline bool IsMatch(
    const coin::proto::ProductFillElement& x,
    const coin::proto::ProductFillElement& y) {
  CHECK(x.source().empty()) << x.DebugString();
  const bool c1 = x.order_id() == y.order_id();
  const bool c2 = std::fabs(x.price() - y.price()) < 1e-8;
  const bool c3 = std::fabs(x.qty() - y.qty()) < 1e-8;
  const bool c4 = y.source() != "match";
  return c1 && c2 && c3 && c4;
}

inline bool FindMatch(
    coin::proto::ProductFillElement* new_fill,
    std::vector<coin::proto::ProductFillElement>* fill_list) {
  for (auto& fill : *fill_list) {
    CHECK(!fill.fill_id().empty()) << fill.DebugString();
    if (IsMatch(*new_fill, fill)) {
      new_fill->set_source("match");  // Mark matched already.
      fill.set_source("match");       // Mark matched already
      new_fill->set_fill_id(fill.fill_id());
      return true;
    }
  }
  return false;
}

}  // namespace

// If ws fill receive first (almost always),
// fill_id is generated, later rest fill will try to
// find the match ws_fill, and add fill_type.
// If rest_fill cannot found match ws_fill, which means
// ws fill is late, then we will use rest_fill,
// later when ws arrives, it is ignored.
class NewFillDetector {
 public:
  using FillList = std::vector<coin::proto::ProductFillElement>;
  using Container = std::map<std::string, FillList>;

 public:
  bool UpdateByWsFill(coin::proto::ProductFillElement* fill) {
    CHECK(!fill->exchange_order_id().empty()) << fill->DebugString();
    // Must have fill id in Okex swap case.
    CHECK(!fill->fill_id().empty()) << fill->DebugString();
    const auto& eoid = fill->exchange_order_id();

    if (FindMatch(fill, &rest_fill_[eoid])) {
      // If found previous ws fill, return true, add fill_type info
      // fill_id is changed to rest fill's fill id
      ws_fill_[eoid].push_back(*fill);
      return false;
    } else {
      ws_fill_[eoid].push_back(*fill);
      return true;
    }
  }

  bool UpdateByRestFill(coin::proto::ProductFillElement* fill) {
    CHECK(!fill->exchange_order_id().empty()) << fill->DebugString();
    // Must have fill id in Okex swap case.
    CHECK(!fill->fill_id().empty()) << fill->DebugString();

    if (fill_ids_.count(fill->fill_id()) > 0) {
      // If known already, just ignore.
      return false;
    }

    fill_ids_.insert(fill->fill_id());
    const auto& eoid = fill->exchange_order_id();
    if (FindMatch(fill, &ws_fill_[eoid])) {
      // If found previous ws fill, return true, add fill_type info
      // fill_id is changed to ws fill's fill id
      rest_fill_[eoid].push_back(*fill);
      return false;
    } else {
      rest_fill_[eoid].push_back(*fill);
      return true;
    }
  }

 private:
  Container ws_fill_;
  Container rest_fill_;
  std::set<std::string> fill_ids_;
};
